共计 9247 个字符,预计需要花费 24 分钟才能阅读完成。
更新记录
2023-09-06
修改 n9e_wechatbot_text.py
代码
- 原始的提取阈值逻辑有误,原函数如下,只是简单地判断运算符是否出现在 prom_ql 字符串中,而没有考虑到运算符的位置和上下文。当标签中含有运算符时,不会正确提取阈值。
def extract_threshold(prom_ql): # 提取阈值的逻辑 threshold = '' if prom_ql: operators = ['!=', '>=', '<=', '>', '<', '='] for operator in operators: if operator in prom_ql: threshold = operator + ' ' + prom_ql.split(operator)[-1].strip() break return threshold
-
改为正则匹配,假设运算符只包含连续的
=
,!=
,>
,<
,>=
,<=
字符,并且阈值是一个浮点数或整数。def extract_threshold(prom_ql): threshold = '' if prom_ql: pattern = r'([!=<>]+)\s*([\d.]+)' match = re.search(pattern, prom_ql) if match: operator = match.group(1) value = match.group(2) threshold = operator + ' ' + value return threshold
2023-09-04
修改 n9e_wechatbot_text.py
代码
- 当告警恢复时,夜莺原始数据
trigger_value
携带的不是恢复状态的当前值,而是最后一次触发告警的值。在代码中增加判断,当告警恢复时,不打印当前值。
2023-09-03
修改 n9e_wechatbot_text.py
代码
- 增加提取阈值。原始数据不包含阈值,从 prom_ql 中提取阈值并通知
- 增加 当告警标题包含
率
时,将触发值
和阈值
格式化为百分比数 - 优化告警持续时间的显示格式
效果如图:
背景
在公司的生产环境中,SRE 有内部开发的告警系统。但是它并不能部署在线下满足个人服务的使用。
最开始我使用了 prometheus
的原生告警组件:alertmanger
,大体上能满足我的需求。但是细节方面令人不满意,纯 yaml 的方式也不够直观,维护起来不方便。
个人使用不同于生产环境,我的诉求有以下几点:
- 支持发送告警到个人微信,我不希望在移动设备上通过其他 APP 来接收告警
- 支持自定义告警模版,方便在移动设备上一眼可分析告警
- 有 web 端进行告警的管理和维护
- 支持从自定义 prometheus 数据源中查询数据
抛开各大云提供商内置的云监控,其实社区上能够选择的告警系统并不算多
- alertmanger
- hertzbeat
- zabbix
- openfalcon
- 夜莺
zabbix 不够贴近云原生场景,它更适合于传统 IDC 环境下,对于各物理资源的监控和告警。
openfalcon 基本已经停止维护。
alertmanger 个人使用,不进行二次开发前提下,对于规则的管理比较困难。
hertzbeat 是一个新兴的监控告警解决方案,可惜它的耦合度太高,监控 告警 绘图都必须依赖它自己的组件。而在 prometheus 几乎已经成为云原生监控事实标准的情况下,我并不希望使用它,我仅仅需要一个能够满足接入数据源,方便有效管理告警的设施。
最终我选择了夜莺,实际部署调试后,可以满足我的告警需求。
夜莺是什么
夜莺对于运维来说肯定不陌生,毕竟社区上成熟的告警方案就那么几家。这里不多赘述,参考官方链接:夜莺官网。
部署
参考链接:夜莺官方文档
官方文档提供了三种部署方案:
- Docker Compose
- 裸机部署
- 高级部署:用于下沉告警引擎,适用于复杂机房拓扑的情况
本文选用了裸机部署的方式。
部署环境:
- 操作系统:CentOS 7.8
- 夜莺版本:v6.1.0
- MySQL 版本:5.5.68-MariaDB
- Redis 版本:3.2.12
其实夜莺部署对于这些组件的版本并没有严格要求,各位自行测试。
MySQL & Redis
因为是个人测试环境,这里使用最简单的方案安装依赖的组件,并不过多考虑版本,密码和其他优化问题。
# 安装和配置mysql
yum -y install mariadb*
systemctl enable mariadb
systemctl restart mariadb
mysql -e "SET PASSWORD FOR 'root'@'localhost' = PASSWORD('1234');"
# 安装和配置redis
yum install -y redis
systemctl enable redis
systemctl restart redis
夜莺
部署最新版 v6.10
# 下载安装包
mkdir /usr/local/n9e && cd /usr/local/n9e
wget https://github.com/ccfos/nightingale/releases/download/v6.1.0/n9e-v6.1.0-linux-amd64.tar.gz
tar xf n9e-v6.1.0-linux-amd64.tar.gz
# 查看相关文件
# ls
cli docker etc integrations n9e n9e-cli n9e-edge n9e.log n9e.sql n9e-v6.1.0-linux-amd64.tar.gz nohup.out
# 导入数据库表结构
mysql -uroot -p1234 < n9e.sql
# 测试启动n9e
./n9e
# 测试通过后,配置systemd管理n9e
# vim /etc/systemd/system/n9e_alert.service
[Unit]
Description=n9e_alert
After=network.target
[Service]
User=root
WorkingDirectory=/usr/local/n9e
Environment="PATH=/usr/local/bin:/usr/bin:/bin"
ExecStart=/usr/local/n9e/n9e > /usr/local/n9e/run.log 2>&1
Restart=always
[Install]
WantedBy=multi-user.target
# 启动和配置开机自启
systemctl enable n9e_alert.service
systemctl restart n9e_alert.service
# 检查服务状态
# systemctl status n9e_alert.service
● n9e_alert.service - n9e_alert
Loaded: loaded (/etc/systemd/system/n9e_alert.service; enabled; vendor preset: disabled)
Active: active (running) since Sat 2023-09-02 13:17:59 CST; 4h 15min ago
Main PID: 26578 (n9e)
Tasks: 8
Memory: 77.7M
CGroup: /system.slice/n9e_alert.service
└─26578 /usr/local/n9e/n9e > /usr/local/n9e/run.log 2>&1
至此安装已经完成,访问地址为 `http://$ip:17000`
调试
配置 nginx 反代
此步骤按照个人需求酌情添加,nginx 配置示例:
upstream n9e {
server 192.168.2.10:17000;
}
server {
listen 80;
server_name n9e.***.com;
return 301 https://$server_name;
}
server {
listen 443 ssl http2;
server_name n9e.***.com;
ssl_certificate /root/.acme.sh/*.***.com/fullchain.cer;
ssl_certificate_key /root/.acme.sh/*.***.com/*.***.com.key;
ssl_session_timeout 5m;
ssl_protocols TLSV1 TLSv1.1 TLSv1.2;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_prefer_server_ciphers on;
location / {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_redirect http:// https://;
proxy_pass http://n9e;
}
}
web 端调试
成员配置
默认的账号密码为:root
, root.2020
登录后自行修改密码和新增需要的用户。
点击 人员组织
–> 用户管理
如果要使用企业微信机器人进行告警,截取机器人的 webhook 中的 key,填入到联系方式中。
团队管理
新建的成员不能直接接收告警,需要将成员添加在团队中,后续的告警规则配置中再将告警通知媒介设置为指定团队。
点击 人员组织
–> 团队管理
–> 新增
数据源配置
添加 prometheus 数据源,请确保你的 prometheus 能够被 n9e 服务器 IP 访问。
点击 系统配置
–> 数据源
告警通知配置
默认所有的 通知媒介
在进行 告警规则
的时候都可选,但是因为我们不需要,而且也没有在 用户管理
进行配置 联系方式
,所以这里隐藏掉不需要的告警方式,仅留下 wecom
。
点击 系统配置
–> 通知设置
–> 通知媒介
告警规则配置
配置一个简单的告警进行验证,例如 由 node_exporter
采集的 CPU使用率
。
点击 告警管理
–> 告警规则
–> 新增
必填参数和选项有:
- 规则名称
- 数据源类型
- 关联数据源
- PromQL
- 触发告警级别
- 执行频率
- 持续时长
- 立即启用
- 生效时间
- 通知媒介
- 告警接收组
- 启用恢复通知
- 重复通知间隔
- 最大发送次数
此处我有一些不满意,因为可以看到 夜莺 不支持设定查询语句查询到的值类型,这个功能在我之前使用的 alertmanger 中是能够满足的。我希望使用 百分率 方式发出,后续的效果可以看到,只能发出为普通的数值。
模拟告警
刚才的 promql 含义是,当 CPU 使用率 > 1% 即告警,经测试能成功发出告警。
在企业微信中,效果如下:
其实普通微信,也可以查看企业微信中的消息,具体操作方式参考链接:百度经验
但是实测效果并不好:
查阅了下原因,是因为内置的通知模版语法为 markdown
格式,并且还无法修改
点击 系统配置
–> 通知模版
–> wecom
并且我测试新建告警模版,也无法变更模版格式
其实到这儿,需求算是可以基本完成。但是我实在不希望单独使用企业微信来接受告警。
个人微信在接收企业微信消息时,只有 text
格式能够被正常的展示。
查阅 企业微信的接口文档,发现其实支持 text
格式,只是 夜莺 没有做兼容。好的,那我自己来解决。
编写 webhook 程序回调企业微信接口
夜莺支持当触发告警时,不仅将告警消息发动到内置的通知媒介,还支持 webhook 方式回调。
那么我们编写一个 python 程序,用于接受夜莺的告警消息,再将其格式化 text 发送到企业微信机器人接口,就能够满足我们在个人微信接收告警的需求。
Python 脚本配置
程序的功能:
- 启动 5000 端口用于接受夜莺发出的通知
- 格式化接受到的数据,原始数据字段繁多 ,提取需要的字段
- 如果想查看原始数据,修改此 python 脚本,将收到的夜莺通知消息不经过格式化,直接打印出来即可
- 发送到企业微信机器人 webhook
操作如下:
# 创建工作目录
mkdir /usr/local/wechat-alert/ && cd /usr/local/wechat-alert/
# 安装依赖
pip install flask requests
# 编写 py 程序
# vim n9e_wechatbot_text.py
from flask import Flask, request
import requests
import json
import datetime
app = Flask('n9e_wechatbot_text')
@app.route('/webhook', methods=['POST'])
def webhook():
data = request.get_json() # 获取POST请求的JSON数据
send_to_wechat(data) # 发送消息到企业微信
return 'OK'
def send_to_wechat(data):
url = 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=4557416b-d3be-430a-b3dd-50d26d271323'
headers = {'Content-Type': 'application/json'}
formatted_data = format_data(data) # 格式化数据
payload = {
'msgtype': 'text',
'text': {
'content': formatted_data
}
}
response = requests.post(url, headers=headers, json=payload)
if response.status_code == 200:
print('消息发送成功')
else:
print('消息发送失败')
def format_data(data):
is_recovered = data.get('is_recovered', False)
severity = data.get('severity', '')
rule_name = data.get('rule_name', '')
note = data.get('note', '')
tags = data.get('tags', [])
trigger_value = data.get('trigger_value', '')
last_eval_time = format_timestamp(int(data.get('last_eval_time', 0)))
first_trigger_time = format_timestamp(int(data.get('first_trigger_time', 0)))
prom_ql = data.get('rule_config', {}).get('queries', [{}])[0].get('prom_ql', '')
rule_note = data.get('rule_note', '')
formatted_data = ''
if is_recovered:
formatted_data += "✅[已恢复] "
else:
formatted_data += "❌[告警中] "
formatted_data += f"{rule_name}\n告警级别:{severity}"
if tags:
formatted_data += "\n标签:"
for tag in tags:
if tag.startswith('rulename='):
continue
formatted_data += f"\n - {tag}"
if rule_note:
formatted_data += f"\n备注:{rule_note}"
formatted_data += f"\n\n阈值:{format_value(extract_threshold(prom_ql), rule_name)}"
if not is_recovered:
formatted_data += f"\n当前值:{format_value(trigger_value, rule_name)}"
formatted_data += f"\n\n首次触发时间:{first_trigger_time}"
formatted_data += f"\n当前时间:{last_eval_time}"
formatted_data += f"\n持续时间:{format_duration(first_trigger_time, last_eval_time)}"
if not tags: # 添加条件判断,当标签列表为空时,返回 None
return None
return formatted_data.strip()
def format_value(value, rule_name):
if '率' in rule_name:
if value:
operator, threshold = split_operator_threshold(value)
threshold = convert_to_percentage(threshold)
return f"{operator} {threshold}"
return value
def split_operator_threshold(value):
operators = ['!=', '>=', '<=', '>', '<', '=']
for operator in operators:
if operator in value:
parts = value.split(operator)
return operator, parts[-1].strip()
return '', value
def convert_to_percentage(value):
try:
value = float(value)
percentage = value * 100
return f"{percentage:.2f}%"
except ValueError:
return value
def extract_threshold(prom_ql):
# 提取阈值的逻辑
threshold = ''
if prom_ql:
operators = ['!=', '>=', '<=', '>', '<', '=']
for operator in operators:
if operator in prom_ql:
threshold = operator + ' ' + prom_ql.split(operator)[-1].strip()
break
return threshold
def format_timestamp(timestamp):
if timestamp:
dt = datetime.datetime.fromtimestamp(timestamp)
return dt.strftime('%Y-%m-%d %H:%M:%S')
return ''
def format_duration(start_time, end_time):
if start_time and end_time:
start_datetime = datetime.datetime.strptime(str(start_time), '%Y-%m-%d %H:%M:%S')
end_datetime = datetime.datetime.strptime(str(end_time), '%Y-%m-%d %H:%M:%S')
duration = end_datetime - start_datetime
days = duration.days
hours = duration.seconds // 3600
minutes = (duration.seconds % 3600) // 60
seconds = duration.seconds % 60
formatted_duration = ''
if days > 0:
formatted_duration += f"{days}d "
if hours > 0:
formatted_duration += f"{hours}h "
if minutes > 0:
formatted_duration += f"{minutes}m "
if seconds > 0 or duration.total_seconds() < 60: # 添加判断条件,如果持续时间小于一分钟,直接返回秒数
formatted_duration += f"{seconds}s"
return formatted_duration.strip()
return ''
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
# 配置 systemd 管理此服务
# vim /etc/systemd/system/n9e_wechatbot_text.service
[Unit]
Description=Flask App - n9e_wechatbot_text
After=network.target
[Service]
User=root
WorkingDirectory=/usr/local/wechat-alert
Environment="PATH=/usr/local/bin:/usr/bin:/bin"
ExecStart=/usr/bin/python3 /usr/local/wechat-alert/n9e_wechatbot_text.py > /usr/local/wechat-alert/run.log 2>&1
Restart=always
[Install]
WantedBy=multi-user.target
# 配置 systemd 管理此服务
systemctl enable n9e_wechatbot_text
systemctl start n9e_wechatbot_text
# 检查服务状态
systemctl status n9e_wechatbot_text
告警规则配置 回调 webhook
可以设置全局的 webhook 回调
点击 系统配置
–> 通知设置
–> 回调地址
也可以设置单个规则的 webhook 回调
点击 告警管理
–> 告警规则
模拟告警
还是和之前一样,我们配置将告警规则阈值调低
已经可以在个人微信中正确接收告警消息,效果如下:
总结
万能的组件永远不会存在,个性化的诉求需要有一定的开发能力做支撑。
对于告警来说,核心的是作为告警引擎的能力。这方面来讲,alertmanger & 夜莺 & zabbix 是经过市场检验的。
但是二开插件的难度,日常使用和维护,也是衡量好与坏的标准。
我目前只将夜莺作为告警引擎来使用,已经完全可以满足我当下的需要。
它的其他功能很多,例如告警自愈,定制采集器等,也值得琢磨。等后续有需求时再进一步的研究。
本文属于专题:Prometheus 监控
- 使用 Redis Exporter 监控 Redis
- Grafana 备份、迁移与升级
- 云原生监控 Kube-Prometheus
- Prometheus 集成 Nginx 监控
- Prometheus 查询指定时间范围内的峰值或均值
- 云监控接入本地Prometheus
- 使用 MySQL Exporter 监控MySQL
- 个人微信接收夜莺告警消息
- PushGateway 报错:too many open files
- Blackbox 网络监控
- node_exporter 添加自定义指标
- Python 实现资源水位巡检
- Prometheus 替代方案:VictoriaMetrics
- 使用 node_exporter 实现路由器监控